package me.zzp.provider;

import me.zzp.domain.Condition;
import me.zzp.domain.Order;
import me.zzp.domain.Pageable;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author zhangzepeng
 */
public abstract class DynamicConditionProvider {

    public final static String PREFIX_CONDITION = "condition";
    public final static String PREFIX_PARAMETER = "parameter";
    public final static String PREFIX_PAGEABLE = "pageable";

    protected String where(Map<String, Object> args) {
        Condition condition = (Condition) args.get(PREFIX_CONDITION);
        if (condition == null) {
            return "";
        }

        Map<String, Object> parameters = (Map<String, Object>) args.get(PREFIX_PARAMETER);
        String expression = toSql(condition, parameters, new AtomicInteger(0));
        if (expression != null && !expression.isEmpty()) {
            expression = " where ".concat(expression);
        } else {
            expression = "";
        }
        return expression;
    }

    protected String paging(Map<String, Object> args) {
        StringBuilder sql = new StringBuilder();

        Pageable pageable = (Pageable) args.get(PREFIX_PAGEABLE);
        if (pageable != null) {
            if (pageable.isPaging()) {
                sql.append(String.format(" limit %d offset %d", pageable.getSize(), pageable.getOffset()));
            }
            if (pageable.getSort() != null && !pageable.getSort().isEmpty()) {
                sql.append(" order by").append(pageable.getSort().stream().map(Order::toString).collect(Collectors.joining(", ")));
            }
        }

        return sql.toString();
    }

    private static String toSql(Condition condition, Map<String, Object> parameters, AtomicInteger index) {
        String sql = "";
        switch (condition.getOperator()) {
            case "and":
            case "or": {
                sql = condition.getParameters().stream()
                        .map(child -> toSql((Condition) child, parameters, index))
                        .collect(Collectors.joining(String.format(" %s ", condition.getOperator())));
            } break;

            case "not": {
                sql = "not ".concat(toSql((Condition) condition.getParameters().get(0), parameters, index));
            } break;

            case "=":
            case "!=":
            case ">":
            case ">=":
            case "<":
            case "<=":
            case "like":
            case "not like": {
                String name = String.format("p%d", index.incrementAndGet());
                String placeholder = String.format("#{%s.%s}", PREFIX_PARAMETER, name);
                String column = condition.get(0);
                Object value = condition.get(1);

                sql = String.format("%s %s %s", column, condition.getOperator(), placeholder);
                parameters.put(name, value);
            } break;

            case "between":
            case "not between": {
                String fromName = String.format("p%d", index.incrementAndGet());
                String toName = String.format("p%d", index.incrementAndGet());

                String column = condition.get(0);
                Object from = condition.get(1);
                Object to = condition.get(2);

                sql = String.format("%s %s %s and %s", column, condition.getOperator(), from, to);
                parameters.put(fromName, from);
                parameters.put(toName, to);
            } break;

            case "in":
            case "not in": {
                StringBuilder builder = new StringBuilder();

                String column = condition.get(0);
                builder.append(column).append(' ').append(condition.getOperator()).append(" (");
                int offset = -1;
                for (Object value : condition.getParameters()) {
                    if (offset < 0) {
                        offset++;
                        continue;
                    } else if (offset > 0) {
                        builder.append(", ");
                    }
                    String name = String.format("p%d", index.incrementAndGet());
                    String placeholder = String.format("#{%s.%s}", PREFIX_PARAMETER, name);

                    builder.append(placeholder);
                    parameters.put(name, value);

                    offset++;
                }
                builder.append(')');

                sql = builder.toString();
            } break;
        }
        return "(".concat(sql).concat(")");
    }

    public abstract String countBy(Map<String, Object> args);
    public abstract String findBy(Map<String, Object> args);
    public abstract String updateBy(Map<String, Object> args);
    public abstract String deleteBy(Map<String, Object> args);
}
